#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Fields.h"

#include "Class.h"
#include "Look.h"
#include "GapText.h"
#include "Manager.h"
#include "RegularExp.h"
#include "Clipper.h"
#include "TextView.h"
#include "Alert_e.h"
#include "Window.h"
#include "Math.h"
#include "ET_stdio.h"
#include "String.h"

//---- TextViewOverlay ---------------------------------------------------------

static TextViewOverlay *gTVO;

ONEXIT(Fields)
{
	SafeDelete(gTVO);
}

TextViewOverlay::TextViewOverlay()
{
	currentfield= 0;
	ct= 0;
	cl= 0;
	tv= 0;
	noupdate= FALSE;
}

TextViewOverlay::~TextViewOverlay()
{
	currentfield= 0;
	if (ct) {
		Object *op;
		Iter next(ct->GetObserverIter());
		while (op= next())
			ct->RemoveObserver(op);
		SafeDelete(ct);
	}
	//SafeDelete(cl);
	SafeDelete(tv);
}

bool TextViewOverlay::CopyInStr(EditField *ef, char *buf, int sz)
{
	if (ef == currentfield) {
		ct->CopyInStr((byte*)buf, sz, 0, ct->Size());
		return FALSE;
	}
	return TRUE;
}

bool TextViewOverlay::Size(EditField *ef, int *sz)
{
	if (ef == currentfield) {
		*sz= ct->End();
		return FALSE;
	}
	return TRUE;
}

bool TextViewOverlay::KbdFocus(EditField *ef, bool in, bool wordwrap)
{
	if (gDebug)
		fprintf(stderr, "TextViewOverlay::KbdFocus(0x%08x, %d) old:0x%08x\n",
														ef, in, currentfield);
	if (in) {
		if (currentfield != 0) {
			if (gDebug)
				fprintf(stderr, "KbdFocus(in): oops\n");
			return FALSE;
		}
		if (ef)
			ef->Reveal();
		const char *es= ef->GetStringForEditing();
		currentfield= ef;
		currentfield->SetFlag(eEditFieldHasOverlay);

		if (ct == 0) {
			ct= GetOverlayText(es);
		} else {
			ct->ReplaceWithStr((byte*)es);
			cl->RevealRect(Rectangle(gPoint1), gPoint1);
		}
		//ct->SetFont(currentfield->GetFont(), 0, ct->Size());
		ct->SetFont(currentfield->GetFont());
		if (gToken.Code != eEvtLeftButton)
			tv->SelectAll(FALSE);

		ct->AddObserver(currentfield);
		cl->SetContainer(currentfield);
		tv->SetNextHandler(currentfield);
		//cl->Open();

		if (wordwrap)
			tv->SetWordWrap(TRUE);

		currentfield->SetExtent(currentfield->GetExtent());
		currentfield->SetOrigin(currentfield->GetOrigin());
		currentfield->ForceRedraw();
	} else {
		if (currentfield != ef) {
			if (gDebug) {
				fprintf(stderr, "KbdFocus(out): oops %d\n", currentfield);
			}
			return FALSE;
		}
		if (!Sync(currentfield))
			return FALSE;
		tv->SetWordWrap(FALSE);
		currentfield->ForceRedraw();
		cl->ClearContainer(currentfield);
		ct->RemoveObserver(currentfield);
		currentfield->ResetFlag(eEditFieldHasOverlay);
		currentfield= 0;
	}
	return tv->KbdFocus(in);
}

Text *TextViewOverlay::GetOverlayText(const char *s)
{
	if (ct == 0) {
		if (s == 0)
			s= "";
		ct= new GapText((byte*)s);
		tv= new TextView(currentfield, gFitRect, ct, TRUE);
		tv->SetDragAndDrop(FALSE);
		tv->SetFlag(eViewNoPrint);
		tv->SetStopChars("\r\n\t");
		tv->ResetFlag(eVObjKbdFocus);
		cl= new Clipper(tv);
		cl->Open();
	}
	return ct;
}

bool TextViewOverlay::Sync(EditField *ef)
{
	if (currentfield == ef) {
		noupdate= TRUE;
		bool valid= currentfield->ValidateString(ct->AsString());
		noupdate= FALSE;
		if (! valid) {
			ct->ReplaceWithStr((byte*)currentfield->GetStringForEditing());
			cl->RevealRect(Rectangle(gPoint1), gPoint1);
			tv->SelectAll(FALSE);
			return FALSE;
		}
		return TRUE;
	}
	return FALSE;
}

bool TextViewOverlay::Draw(EditField *ef, Rectangle r)
{
	if (currentfield == ef) {
		cl->DrawAll(r);
		return FALSE;
	}
	return TRUE;
}

void TextViewOverlay::Update(EditField *ef)
{
	if (currentfield == ef && !noupdate) {
		ct->ReplaceWithStr((byte*)currentfield->GetStringForEditing());
		//if (ct->GetFont() != currentfield->GetFont())
			ct->SetFont(currentfield->GetFont());
		cl->RevealRect(Rectangle(gPoint1), gPoint1);
	}
}

void TextViewOverlay::SetOrigin(EditField *ef, Point at)
{
	if (currentfield == ef)
		cl->SetOrigin(at);
}

void TextViewOverlay::SetExtent(EditField *ef, Point e)
{
	if (currentfield == ef) {
		cl->SetExtent(e);
		if (tv->GetWordWrap())
			tv->SetExtent(Point(e.x, cFit));
	}
}

Command *TextViewOverlay::DispatchEvents(EditField *ef, Point lp, Token &t, Clipper *vf)
{
	if (currentfield == ef)
		return cl->Input(lp, t, vf);
	return 0;
}

void TextViewOverlay::DoSetupMenu(EditField *ef, Menu *m)
{
	if (currentfield == ef)
		tv->DoSetupMenu(m);
}

Command *TextViewOverlay::DoMenuCommand(EditField *ef, int c)
{
	if (currentfield == ef)
		return tv->DoMenuCommand(c);
	return gNoChanges;
}

//---- Field -------------------------------------------------------------------

NewMetaImpl(Field,VObject, (TP(font), T(minwidth)));

Field::Field(int mw, Font *f)
{
	Init(mw, f);
}

Field::Field(int id, int mw, Font *f) : VObject(id)
{
	Init(mw, f);
}

void Field::Init(int mw, Font *f)
{
	font= f;
	minwidth= mw;
	SetFlag(eVObjVFixed);
}

void Field::SetEditable(bool e)
{
	SetFlag(eVObjKbdFocus, e);
	SetFlag(eEditFieldNoBorder, !e);
}

const char *Field::GetStringForDrawing()
{
	return AsString();
}

void Field::SetFont(Font *fp)
{
	font= fp;
}

Metric Field::GetMinSize()
{
	int b= (int)gLook->FieldBorderLayout(TestFlag(eEditFieldNoBorder))->GetValue(this, 0);
	return StaticTextView::MeasureText(GetStringForDrawing(), font,
				minwidth, GetLines()).Expand(gBorder+Point(b));
}

void Field::Draw(Rectangle)
{
	int b= (int)gLook->FieldBorderLayout(TestFlag(eEditFieldNoBorder))->GetValue(this, 0);
	Ink *ink= Enabled() ? gInkBlack : gLook->DisableInk();
	StaticTextView::DrawBoxedText(contentRect.Inset(gBorder+Point(b)),
			GetStringForDrawing(), font, ink, GetLines() > 1);
}

int Field::Compare(Object *op)
{
	return StrCmp(AsString(), Guard(op,Field)->AsString(), -1,
							gStdEncoding->SortMap());
}

bool Field::IsEqual(Object *op)
{
	if (op == 0 || ! op->IsKindOf(Field))
		return FALSE;
	return StrCmp(AsString(), op->AsString(), -1, gStdEncoding->SortMap()) == 0;
}

int Field::GetLines()
{
	return 1;
}

OStream& Field::PrintOn (OStream &s)
{
	VObject::PrintOn(s);
	return s << font SP << minwidth SP;
}

IStream& Field::ReadFrom(IStream &s)
{
	VObject::ReadFrom(s);
	return s >> font >> minwidth;
}

//---- EditField ---------------------------------------------------------------

NewMetaImpl0(EditField,Field);

EditField::EditField(int id, int mw, Font *f) : Field(id, mw, f)
{
	SetFlag(eVObjEnabled | eVObjKbdFocus);
}

EditField::~EditField()
{
	//if (HasOverlay())
	//    GetTextViewOverlay()->KbdFocus(this, FALSE, FALSE);
}

TextViewOverlay *EditField::GetTextViewOverlay()
{
	Manager *m= (Manager*) FindNextHandlerOfClass(Meta(Manager));
	if (m)
		return m->GetTextViewOverlay();

	if (gDebug) {
		fprintf(stderr, "EditField::GetTextViewOverlay\n");
		if (GetNextHandler())
			fprintf(stderr, "GetNextHandler(): %s\n", GetNextHandler()->ClassName());
		else
			fprintf(stderr, "GetNextHandler(): 0\n");
	}

	if (gTVO == 0)
		gTVO= new TextViewOverlay;
	return gTVO;
}

void EditField::Enable(bool enable, bool redraw)
{
	Field::Enable(enable, redraw);
	if (!enable)
		KbdFocus(enable);
}

const char *EditField::GetStringForEditing()
{
	return AsString();
}

void EditField::Draw(Rectangle r)
{
	if (!HasOverlay() || GetTextViewOverlay()->Draw(this, r))
		Field::Draw(r);
	gLook->FieldBorderLayout(TestFlag(eEditFieldNoBorder))->Adorn(this, r, HasOverlay());
}

void EditField::Update(bool redraw)
{
	if (HasOverlay())
		GetTextViewOverlay()->Update(this);
	if (redraw)
		InvalidateRect(contentRect.Inset(gPoint4));
}

void EditField::Sync()
{
	if (HasOverlay())
		GetTextViewOverlay()->Sync(this);
}

static bool lock;

void EditField::DoSetupMenu(Menu *m)
{
	if (HasOverlay() && !lock) {
		lock= TRUE;
		GetTextViewOverlay()->DoSetupMenu(this, m);
		lock= FALSE;
	} else
		Field::DoSetupMenu(m);
}

Command *EditField::DoMenuCommand(int c)
{
	if (HasOverlay() && !lock) {
		lock= TRUE;
		Command *cmd= GetTextViewOverlay()->DoMenuCommand(this, c);
		lock= FALSE;
		return cmd;
	}
	return Field::DoMenuCommand(c);
}

Command *EditField::Input(Point lp, Token &t, Clipper *vf)
{
	if (t.IsKey()) {
		if (t.Code == '\t') {
			Manager *m= (Manager*) FindNextHandlerOfClass(Meta(Manager));
			if (m)
				m->TabFields(t);
			return gNoChanges;
		}
		if ((t.Code == '\r' || t.Code == '\n') && GetLines() <= 1) {
			Manager *m= (Manager*) FindNextHandlerOfClass(Meta(Manager));
			if (m)
				m->TabFields(t);
			return gNoChanges;
		}
	}
	return Field::Input(lp, t, vf);
}

void EditField::SetOrigin(Point at)
{
	Field::SetOrigin(at);
	if (HasOverlay()) {
		int b= (int)gLook->FieldBorderLayout(TestFlag(eEditFieldNoBorder))->GetValue(this, 0);
		GetTextViewOverlay()->SetOrigin(this, at+Point(b));
	}
}

void EditField::SetExtent(Point e)
{
	Field::SetExtent(e);
	if (HasOverlay()) {
		int b= (int)gLook->FieldBorderLayout(TestFlag(eEditFieldNoBorder))->GetValue(this, 0);
		GetTextViewOverlay()->SetExtent(this, e-Point(2*b));
	}
}

bool EditField::KbdFocus(bool in)
{
	if (HasOverlay() != in)
		return GetTextViewOverlay()->KbdFocus(this, in, GetLines() > 1);
	return TRUE;
}

void EditField::Control(int, int part, void *val)
{
	switch (part) {
	case cPartViewSize:
		if (TestFlag(eEditFieldAutoSize)) {
			int b= (int)gLook->FieldBorderLayout(TestFlag(eEditFieldNoBorder))->GetValue(this, 0);
			Point ve(*((Point*)val));
			SetExtent(ve+2*b);
			ExtentChanged(this);
		}
		break;
		
	case cPartSelectionChanged:
	case cPartOriginChanged:
	case cPartExtentChanged:
	case cPartScrollPos:
		break;

	case cPartChangedText:
		Field::Control(GetId(), part, val);
		break;

	default:
		Field::Control(GetId(), part, val);
		break;
	}
}

Command *EditField::DispatchEvents(Point lp, Token &t, Clipper *vf)
{
	Command *cmd;
	if (HasOverlay() && (cmd= GetTextViewOverlay()->DispatchEvents(this, lp, t, vf)))
		return cmd;
	return Field::DispatchEvents(lp, t, vf);
}

bool EditField::ValidateString(const char*)
{
	// called when content in TextView needs to be checked, eg. focus loss
	// override for validation, reformatting and completion of string
	// return FALSE if not acceptable
	return TRUE;
}

void EditField::Open(bool mode)
{
	Field::Open(mode);
}

//---- TextField ---------------------------------------------------------------

NewMetaImpl(TextField,EditField, (TP(text)));

TextField::TextField(int id, int minWidthInChars, Font *f) : EditField(id, 0, f)
{
	text= strsave("");
	minwidth= font->Width('w') * minWidthInChars;
}

TextField::TextField(int id, const char *t, Font *f) : EditField(id, 0, f)
{
	text= strsave(t ? t : "");
	minwidth= font->Width((byte*)text);
}

TextField::TextField(int id, int minWidthInPixel, const char *t)
											: EditField(id, minWidthInPixel)
{
	text= strsave(t ? t : "");
}

TextField::~TextField()
{
	SafeDelete(text);
}

const char *TextField::AsString()
{
	return text;
}

void TextField::SetString(const char *t, bool redraw)
{
	strreplace(&text, t);
	Update(redraw);
	Changed();
}

void TextField::SetFString(bool redraw, const char *va_(fmt), ...)
{
	va_list ap;
	va_start(ap,va_(fmt));
	strvreplace(&text, va_(fmt), ap);
	Update(redraw);
	Changed();
	va_end(ap);
}

void TextField::GetString(char *buf, int sz)
{
	if (!HasOverlay() || GetTextViewOverlay()->CopyInStr(this, buf, sz)) {
		if (text)
			strn0cpy(buf, text, sz);
		else
			strcpy(buf, "");
	}
}

const char *TextField::GetString()
{
	Sync();
	return AsString();
}

int TextField::GetTextSize()
{
	int sz= 0;
	if (!HasOverlay() || GetTextViewOverlay()->Size(this, &sz))
		if (text)
			sz= strlen(text);
	return sz;
}

OStream& TextField::PrintOn (OStream &s)
{
	EditField::PrintOn(s);
	return s.PrintString(text);
}

IStream& TextField::ReadFrom(IStream &s)
{
	char *t;
	EditField::ReadFrom(s);
	s.ReadString(&t);
	SetString(t, FALSE);
	return s;
}

bool TextField::ValidateString(const char *s)
{
	SetString(s, FALSE);
	Control(GetId(), cPartValueChanged, (void*) s);
	return TRUE;
}

//---- MultiLineField ----------------------------------------------------------

NewMetaImpl(MultiLineField,TextField, (T(lines)));

MultiLineField::MultiLineField(int id, int l, int mw, const char *t)
							: TextField(id, mw, t)
{
	lines= Math::Max(0, l);
}

int MultiLineField::GetLines()
{
	return lines;
}

OStream& MultiLineField::PrintOn (OStream &s)
{
	TextField::PrintOn(s);
	return s << lines SP;
}

IStream& MultiLineField::ReadFrom(IStream &s)
{
	TextField::ReadFrom(s);
	return s >> lines;
}

//---- FormatField -------------------------------------------------------------

NewMetaImpl(FormatField,EditField, (TP(format)));

FormatField::FormatField(int id, const char *fmt) : EditField(id)
{
	format= strsave(fmt);
}

FormatField::~FormatField()
{
	SafeDelete(format);
}

OStream& FormatField::PrintOn(OStream &s)
{
	EditField::PrintOn(s);
	return s.PrintString(format);
}

IStream& FormatField::ReadFrom(IStream &s)
{
	EditField::ReadFrom(s);
	SafeDelete(format);
	return s.ReadString(&format);
}

//---- IntField ----------------------------------------------------------------

NewMetaImpl(IntField,FormatField, (T(value), T(minVal), T(maxVal)));

IntField::IntField(int id, int mi, int ma, const char *fmt) : FormatField(id, fmt)
{
	Init(0, mi, ma);
}

IntField::IntField(int id, int val, int mi, int ma, const char *fmt) : FormatField(id, fmt)
{
	Init(val, mi, ma);
}

void IntField::Init(int val, int mi, int ma)
{
	minVal= mi;
	maxVal= ma;
	int lmin= font->Width((byte*)form(format, minVal));
	int lmax= font->Width((byte*)form(format, maxVal));
	minwidth= Math::Max(lmin, lmax);
	SetValue(val, FALSE);
}

const char *IntField::AsString()
{
	return form(format, value);
}

const char *IntField::GetStringForDrawing()
{
	return form(format, value);
}

int IntField::GetValue()
{
	Sync();
	return value;
}

bool IntField::SetValue(int newVal, bool redraw)
{
	newVal= Math::Range(minVal, maxVal, newVal);
	if (newVal != value) {
		value= newVal;
		Update(redraw);
		return TRUE;
	}
	return FALSE;
}

void IntField::GetRange(int &mi, int &ma)
{
	mi= minVal;
	ma= maxVal;
}

void IntField::SetRange(int mi, int ma)
{
	minVal= mi;
	maxVal= ma;
	SetValue(value);
}

OStream& IntField::PrintOn(OStream &s)
{
	FormatField::PrintOn(s);
	return s << value SP << minVal SP << maxVal SP;
}

IStream& IntField::ReadFrom(IStream &s)
{
	int newval;
	FormatField::ReadFrom(s);
	s >> newval >> minVal >> maxVal;
	SetValue(newval, FALSE);
	return s;
}

bool IntField::ValidateString(const char *s)
{
	int newval;
	char *b= strsave(s);
	int rc= sscanf(b, "%d", &newval);
	delete b;

	if (rc != 1) {
		ShowAlert(eAlertNote, "\"%s\" is not a number", b);
		return FALSE;
	}

	if (newval < minVal || newval > maxVal) {
		ShowAlert(eAlertNote, "%d not in range %d-%d", newval, minVal, maxVal);
		return FALSE;
	}
	if (SetValue(newval))
		Control(GetId(), cPartValueChanged, &newval);
	return TRUE;
}

//---- FloatField --------------------------------------------------------------

NewMetaImpl(FloatField,FormatField, (T(value), T(minVal), T(maxVal)));

FloatField::FloatField(int id, double mi, double ma, const char *fmt) : FormatField(id, fmt)
{
	Init(0.0, mi, ma);
}

FloatField::FloatField(int id, double val, double mi, double ma, const char *fmt)
													: FormatField(id, fmt)
{
	Init(val, mi, ma);
}

void FloatField::Init(double val, double mi, double ma)
{
	minVal= mi;
	maxVal= ma;
	int lmin= font->Width((byte*)form(format, minVal));
	int lmax= font->Width((byte*)form(format, maxVal));
	minwidth= Math::Max(lmin, lmax);
	SetValue(val, FALSE);
}

const char *FloatField::AsString()
{
	return form(format, value);
}

const char *FloatField::GetStringForDrawing()
{
	return form(format, value);
}

double FloatField::GetValue()
{
	Sync();
	return value;
}

bool FloatField::SetValue(double newVal, bool redraw)
{
	newVal= Math::Range(minVal, maxVal, newVal);
	if (newVal != value) {
		value= newVal;
		Update(redraw);
		return TRUE;
	}
	return FALSE;
}

void FloatField::GetRange(double &mi, double &ma)
{
	mi= minVal;
	ma= maxVal;
}

void FloatField::SetRange(double mi, double ma)
{
	minVal= mi;
	maxVal= ma;
	SetValue(value);
}

OStream& FloatField::PrintOn(OStream &s)
{
	FormatField::PrintOn(s);
	return s << value SP << minVal SP << maxVal SP;
}

IStream& FloatField::ReadFrom(IStream &s)
{
	double newval;
	FormatField::ReadFrom(s);
	s >> newval >> minVal >> maxVal;
	SetValue(newval, FALSE);
	return s;
}

bool FloatField::ValidateString(const char *s)
{
	double newval;
	char *b= strsave(s);
	int rc= sscanf(b, "%lf", &newval);
	delete b;

	if (rc != 1) {
		ShowAlert(eAlertNote, "\"%s\" is not a number", s);
		return FALSE;
	}

	if (newval < minVal || newval > maxVal) {
		ShowAlert(eAlertNote, "%g not in range %g-%g", newval, minVal, maxVal);
		return FALSE;
	}
	if (SetValue(newval))
		Control(GetId(), cPartValueChanged, &newval);
	return TRUE;
}

//---- RegExpField -------------------------------------------------------------

NewMetaImpl(RegExpField,TextField, (TP(regex), TP(errmsg)));

RegExpField::RegExpField(int id, RegularExp *r, int mw, const char *msg,
					const char *t) : TextField(id, mw, t)
{
	regex= r;
	errmsg= strsave(msg ? msg : "Illegal format \"%s\" ");
}

RegExpField::~RegExpField()
{
	SafeDelete(regex);
	SafeDelete(errmsg);
}

bool RegExpField::ValidateString(const char *s)
{
	TextField::ValidateString(s);
	if (strlen(s) && regex->Match(s) != strlen(s)) {
		ShowAlert(eAlertNote, errmsg, s);
		return FALSE;
	}
	return TRUE;
}

OStream& RegExpField::PrintOn(OStream &s)
{
	TextField::PrintOn(s);
	s << regex SP;
	return s.PrintString(errmsg);
}

IStream& RegExpField::ReadFrom(IStream &s)
{
	TextField::ReadFrom(s);
	s >> regex;
	SafeDelete(errmsg);
	return s.ReadString(&errmsg);
}

